package com.apesay.agent;

import com.apesay.core.ConfigUtils;
import com.apesay.core.ProfilerPlugin;
import javassist.*;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

/**
 * ClassName:PreAgent
 * Reason:	 
 * Date:     2017/11/1 16:16
 *
 * @author sid
 */
public class PreAgent {
    static String beanName = "com.apesay.test.User";
    static String methodName = "isAdult";
    
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("pre start ... ");
        
        out("star",beanName, methodName);
        //1.
        editMethod(beanName,methodName);
        //2.
//        mackNewMethod(beanName,methodName);
        //3.
//        addNewField(beanName,methodName);
        //4.使用类方式编写
//        addNewFieldClass(beanName,methodName,instrumentation);
        
        out("end",beanName, methodName);

        //5.
//        loadPlugin();
    }

    
    /**
     * java.lang.String -> java/lang/String
     * @param javaName
     * @return
     */
    public static String javaNameToJvmName(String javaName) {
        if (javaName == null) {
            throw new NullPointerException("javaName must not be null");
        }
        return javaName.replace('.', '/');
    }
    
    /**
     * 使用插件方式进行类加载
     */
    private static void loadPlugin() {
        HashMap<String,HashMap> ctClasses = new HashMap<String, HashMap>();
        ctClasses.put(ProfilerPlugin.TYPE_JAVASSIST,new HashMap());
        ctClasses.put(ProfilerPlugin.TYPE_ASM,new HashMap());
        List<ProfilerPlugin> loads = PluginsLoader.load(ProfilerPlugin.class);
        for (ProfilerPlugin pp:loads){
            pp.setup(ConfigUtils.getValue(pp.getKey(), false),ctClasses);
//            CtClass ct = pp.setup(ConfigUtils.getValue(pp.getKey(), false),pluginContext);
//            if (null!=ct)
//                ctClasses.put(ct.getName(),ct);
        }
//        for (CtClass ctClass:ctClasses.values()){
//            try {
//                //TODO 确保只被load一次
//                ctClass.toClass();
//            } catch (CannotCompileException e) {
//                e.printStackTrace();
//            }
//        }
        //javassist加载方式
        for (Object obj:ctClasses.get(ProfilerPlugin.TYPE_JAVASSIST).values()) {
            try {
                //TODO 统一的地方进行加载处理，确保只被load一次
                CtClass ctClass = (CtClass) obj;
                ctClass.toClass();
            } catch (CannotCompileException e) {
                e.printStackTrace();
            }
        }

        //asm方式
        HashMap<String,Object> asmMap = ctClasses.get(ProfilerPlugin.TYPE_ASM);
        for (String key:asmMap.keySet()) {
            //尝试调用新的处理
            try {
                byte[] byteArray = (byte[]) asmMap.get(key);

                //这里注意，如果已经被javassist进行加载过；这里就不要重复加载了
                
                //实现默认的ClassLoader进行jvm装载
//                JVMClassLoader jvmClassLoader = new JVMClassLoader();
//                jvmClassLoader.defineLoadingClass(key, byteArray);

                ASMClassLoader loader = new ASMClassLoader();
                //实现自定义的ClassLoader进行装载
                Class<?> exampleClass = loader.defineClass(key, byteArray);

                Object o = exampleClass.newInstance();
                Method setName = exampleClass.getMethod("setName", String.class);
                setName.invoke(o, "asmSid");
                Method isAdult = exampleClass.getMethod("isAdult", null);
                isAdult.invoke(o, null);
                
//            } catch (IllegalAccessException e) {
//                e.printStackTrace();
//            } catch (InvocationTargetException e) {
//                e.printStackTrace();
//            } catch (NoSuchMethodException e) {
//                e.printStackTrace();
//            } catch (InstantiationException e) {
//                e.printStackTrace();
//            } catch (ClassNotFoundException e) {
//                e.printStackTrace();
//            } catch (ASMCompileException e) {
//                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void out(String type,String beanName, String methodName) {
        System.out.println("============ "+type+" class bytes for "+beanName+" : "+methodName);
    }


    /**
     * 增加新的属性处理
     * @param beanName
     * @param methodName
     */
    private static void addNewField(String beanName,String methodName) {
        ClassPool classPool = ClassPool.getDefault();
        try {
            CtClass ctClass = classPool.get(beanName);

            ctClass.addField(CtField.make("private long start = 0l;",ctClass));
            ctClass.addField(CtField.make("private long end = 0l;",ctClass));

            // 获取指定方法名称  
            CtMethod method = ctClass.getDeclaredMethod(methodName, null);
            CtMethod doBefor = CtNewMethod.make("public void doBefor() {}", ctClass);
            doBefor.setBody("{" +
                    "this.start = System.currentTimeMillis();" +
                    "System.out.println(\"doBefor-------执行前\");" +
                    "}");
            ctClass.addMethod(doBefor);
            CtMethod doAfter = CtNewMethod.make("public void doAfter() {}", ctClass);
            doAfter.setBody("{" +
                    "this.end = System.currentTimeMillis();" +
                    "long consumeTime = this.end - this.start;" +
//                    "System.out.println(consumeTime);" +
                    "System.out.println(\"doAfter-------执行时间：\"+consumeTime);" +
                    "}");
            ctClass.addMethod(doAfter);


            // 赋值方法到新方法中  
            CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
            // 修改源方法名称  
            String oldName = method.getName() + "$Impl";
            method.setName(oldName);
            // 修改原方法  
            newMethod.setBody("{" +
                    "doBefor();" +
                    "boolean obj = "+oldName+"($$);" +
                    "doAfter();" +
                    "return obj;" +
                    "}");
            // 将新方法添加到类中  
            ctClass.addMethod(newMethod);
            // 加载重新编译的类  
            ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 增加可执行的方法
     * @param beanName
     * @param methodName
     */
    private static void mackNewMethod(String beanName,String methodName) {
        ClassPool classPool = ClassPool.getDefault();
        try {
            CtClass ctClass = classPool.get(beanName);

            // 获取指定方法名称  
            CtMethod method = ctClass.getDeclaredMethod(methodName, null);

            CtMethod doBefor = CtNewMethod.make("public void doBefor() {}", ctClass);
            doBefor.setBody("{" +
                    "System.out.println(\"doBefor-------执行前\");" +
                    "}");
            ctClass.addMethod(doBefor);

            CtMethod doAfter = CtNewMethod.make("public void doAfter() {}", ctClass);
            doAfter.setBody("{" +
                    "System.out.println(\"doAfter-------执行前\");" +
                    "}");
            ctClass.addMethod(doAfter);


            // 赋值方法到新方法中  
            CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
            // 修改源方法名称  
            String oldName = method.getName() + "$Impl";
            method.setName(oldName);
            // 修改原方法  
            newMethod.setBody("{" +
                    "doBefor();" +
                    "boolean obj = "+oldName+"($$);" +
                    "doAfter();" +
                    "return obj;" +
                    "}");
            // 将新方法添加到类中  
            ctClass.addMethod(newMethod);
            // 加载重新编译的类  
            ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 增加简单的输出处理
     * @param beanName
     * @param methodName
     */
    private static void editMethod(String beanName,String methodName) {
        ClassPool classPool = ClassPool.getDefault();
        try {
            CtClass ctClass = classPool.get(beanName);

            // 获取指定方法名称  
            CtMethod method = ctClass.getDeclaredMethod(methodName, null);
            // 赋值方法到新方法中  
            CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
            // 修改源方法名称  
            String oldName = method.getName() + "$Impl";
            method.setName(oldName);
            // 修改原方法  
            newMethod.setBody("{" +
                    "System.out.println(\"执行前\");" +
                    "boolean obj = "+oldName+"($$);" +
                    "System.out.println(\"执行后\");" +
                    "return obj;" +
                    "}");
            // 将新方法添加到类中  
            ctClass.addMethod(newMethod);
            // 加载重新编译的类  
            ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
